#!/usr/bin/env python
# coding=utf-8
# @FileName :data_process.py
# @Time     :2022/2/10 19:32
# @Author   :ZXC
# @Desc     :
from typing import Union

from loguru import logger

from config.settings import extra_pool
from tools.common import rep_expr, allure_step_no, allure_step, extractor
from tools.db_client import mysql_client
from tools.json_util import JsonUtil
from tools.read_file import ReadFile


class DataProcess(object):
    # 从配置文件读取基本请求头
    header = ReadFile.read_config('$.request_headers')

    @classmethod
    def handle_data(cls, variable: dict) -> dict:
        """
        数据处理
        :param variable: 数据，传入的是可转换字典,其中可以包含变量表达式
        return 处理之后的dict类型的字典数据
        """
        if variable:
            variable = JsonUtil.dict_to_str(variable)
            # 执行替换参数中的变量和方法（$）
            data = rep_expr(variable, extra_pool)
            # 反序列化
            variable = JsonUtil.str_to_dict(data)
        logger.debug(f'数据处理完成：{variable}')
        return variable

    @classmethod
    def handle_path(cls, path_str: str) -> str:
        """
        路径参数处理
        :param path_str: 带提取表达式的字符串 /${id}/state/${create_time}
        上述内容表示，从extra_pool字典里取到key为id 对应的值，假设是500，后面${create_time} 类似， 假设其值为 1605711095 最终提取结果
        return  /511/state/1605711095
        """
        logger.debug(f'请求地址处理开始')
        default = ReadFile.read_config("$.server.default")
        base_url = str(ReadFile.read_config(f'$.server.{default}')).rstrip('/')
        path_str = rep_expr(path_str, extra_pool)
        url = base_url + path_str
        # allure报告 步骤 请求地址
        allure_step_no(f'请求地址: {url}')
        logger.info(f'请求地址处理完成：{url}')
        return url

    @classmethod
    def handle_header(cls, header_dict: dict) -> dict:
        """处理header， 将用例中的表达式处理后 追加到基础header中
        :header_str: 用例栏中的header
        return header:
        """
        logger.debug(f'请求头处理开始')
        if header_dict is None:
            header_dict = {}

        cls.header.update(header_dict)
        cls.header = cls.handle_data(cls.header)
        allure_step('请求头', cls.header)
        logger.info(f'请求头处理完成：{cls.header}')
        return cls.header

    @classmethod
    def handle_request_data(cls, variable: dict) -> dict:
        """
        请求数据处理
        :param variable: 请求数据，传入的是可转换字典,其中可以包含变量表达式
        return 处理之后的dict类型的字典数据
        """
        logger.debug(f'请求数据处理开始')
        variable = cls.handle_data(variable)
        allure_step('请求数据', variable)
        logger.info(f'请求数据处理完成：{variable}')
        return variable

    @classmethod
    def handler_files(cls, file_obj: dict) -> Union[list, dict]:
        """
        file对象处理方法
        :param file_obj: 上传文件使用，格式：接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"]
        实例- 单个文件: &media&D:
        """
        logger.debug(f'请求文件处理开始')
        files = None
        if file_obj:
            for k, v in file_obj.items():
                file_type = k + '.' + str(v).rsplit('.', maxsplit=1)[-1]
                files = {'file': (file_type,
                                  open(v, 'rb'),
                                  'multipart/form-data; boundary=----WebKitFormBoundary9uCIewqcWegG3mq8')}
        allure_step('上传文件', file_obj)
        logger.info(f'请求文件处理完成：{file_obj}')
        return files

    @classmethod
    def handle_response_code(cls, code):
        extra_pool.update({'response_code': code})

    @classmethod
    def handle_extra(cls, extra_dict: dict, response: dict):
        """
        处理提取参数栏
        :param extra_dict: excel中 提取参数栏内容，需要是 {"参数名": "jsonpath提取式"} 可以有多个
        :param response: 当前用例的响应结果字典
        """
        logger.debug(f'提取参数处理开始')
        if extra_dict:
            for k, v in extra_dict.items():
                extra_pool[k] = extractor(response, v)
                logger.debug(f'加入依赖字典,key: {k}, 对应value: {extra_pool[k]}')
        logger.debug(f'提取参数处理完成')

    @classmethod
    def handle_mysql(cls, sql: dict):
        """
        处理sql，如果sql执行的结果不会空，执行sql的结果和参数池合并
        :param sql: 支持单条或者多条sql，其中多条sql使用 ; 进行分割
            多条sql,在用例中填写方式如下select * from user; select * from goods 每条sql语句之间需要使用 ; 来分割
            单条sql,select * from user 或者 select * from user;
        :return:
        """
        result = None
        sql = rep_expr(JsonUtil.dict_to_str(sql), extra_pool)
        sql = JsonUtil.str_to_dict(sql)
        if isinstance(sql, dict):
            for k, sql in sql.items():
                sql = sql.strip()
                if sql == '':
                    continue
                # 查后置sql
                result = mysql_client.execute_sql(sql)
                if result is not None:
                    logger.info(f'更新参数池==>{result}')
                    # 将查询结果添加到响应字典里面，作用在，接口响应的内容某个字段 直接和数据库某个字段比对，在预期结果中
                    # 使用同样的语法提取即可
                    extra_pool.update(result)
        return result

    @classmethod
    def handle_before(cls, before_dict: dict):
        """
        前置操作
        :param before_dict:
        :return:
        """
        # 前置操作
        if before_dict:
            for before_name, before_action in before_dict.items():
                logger.info(f"执行前置置操作==>{before_name}")
                before_action_result = rep_expr(before_action, extra_pool)
                allure_step(f"前置操作=={before_name}=={before_action}==执行结果", before_action_result)
                logger.info(f"前置操作=={before_name}=={before_action}==执行结果==>{before_action_result}")

    @classmethod
    def handle_after(cls, response: dict, after_dict: dict):
        """
        后置操作，断言和别的操作都集成
        :param response:
        :param after_dict: 后置操作字典对象
        :return:
        """
        # 后置操作
        if after_dict:
            for after_name, after_action in after_dict.items():
                logger.info(f"执行后置操作==>{after_name}")
                # 判断是断言还是别的操作
                if isinstance(after_action, dict):
                    logger.info(f"执行后置断言==>{after_action}")
                    expect_dict = after_action
                    # 参数池
                    logger.info(f"当前可用参数池==>{extra_pool}")
                    allure_step("当前可用参数池", extra_pool)
                    for k, v in expect_dict.items():
                        # 执行断言
                        # 保留老的值，用来展示
                        old_k = k
                        old_v = v
                        # 处理实际值的key
                        k = rep_expr(k, extra_pool)
                        # 获取需要断言的实际结果部分
                        actual = extractor(response, k)
                        logger.info(
                            f'断言{old_k} == {old_v},实际结果{type(actual)}:{actual} | 预期结果{type(v)}:{v} \n断言结果 {actual == v}'
                        )
                        allure_step(f'断言{old_k} == {old_v}', f'实际结果{type(actual)}:{actual} = 预期结果{type(v)}:{v}')
                        try:
                            assert actual == v
                        except Exception:
                            raise AssertionError(
                                f'断言{old_k} == {old_v} -|- 实际结果{type(actual)}:{actual} || 预期结果{type(v)}: {v}')
                else:
                    # 非断言操作
                    after_action_result = rep_expr(after_action, extra_pool)
                    allure_step(f"后置操作=={after_name}=={after_action}==执行结果", after_action_result)
                    logger.info(f"后置操作=={after_name}=={after_action}==执行结果==>{after_action_result}")

    @classmethod
    def handle_after2(cls, after_dict: dict):
        """
        废弃
        后置操作
        :param after_dict: 后置字典对象
        :return:
        """
        # 后置操作
        if after_dict:
            for after_name, after_action in after_dict.items():
                logger.info(f"执行后置操作==>{after_name}")
                after_action_result = rep_expr(after_action, extra_pool)
                allure_step(f"后置操作=={after_name}==执行结果", after_action_result)
                logger.info(f"后置操作=={after_name}==执行结果==>{after_action_result}")

    @classmethod
    def assert_result(cls, response: dict, expect_dict: dict):
        """
        废弃
        预期结果实际结果断言方法
        :param response: 实际响应结果
        :param expect_dict: 预期响应内容，从excel中读取
        return None
        """

        # 参数池
        logger.info(f"当前可用参数池==>{extra_pool}")
        allure_step("当前可用参数池", extra_pool)

        # 断言
        logger.debug(f'用例断言开始')
        if isinstance(expect_dict, dict):
            index = 0
            for k, v in expect_dict.items():
                # 处理实际值的key
                k = rep_expr(k, extra_pool)
                # 获取需要断言的实际结果部分
                actual = extractor(response, k)
                index += 1
                logger.info(f'第{index}个断言,实际结果:{actual} | 预期结果:{v} \n断言结果 {actual == v}')
                allure_step(f'第{index}个断言', f'实际结果:{actual} = 预期结果:{v}')
                try:
                    assert actual == v
                except Exception:
                    raise AssertionError(
                        f'第{index}个断言失败 -|- 实际结果:{actual} || 预期结果: {v}')
            else:
                logger.info(f'执行用例{"*" * 10}成功{"*" * 10}')
        #
        # # 后置操作
        # cls.handle_after(after_dict=after_dict)


if __name__ == '__main__':
    pass
